// PanelSplitFile.cpp

#include "StdAfx.h"

#include "Directory.h"

#include "Common/System/Error.h"
#include "Common/Stream/FileIO.h"
#include "Common/FileSystem/FileDir.h"
#include "Common/FileSystem/FileFind.h"

#include "../../../GUI/ExtractRes.h"

#include "../../resource.h"

#include "../../CopyDialog.h"
#include "../../ProgressDialog2.h"
#include "Common/String/FormatUtils.h"
#include "Common/Lang/LangUtils.h"
#include "../../SplitDialog.h"
#include "Common/FileSystem/SplitUtils.h"

#include "../Windows.h"
#include "../Buffer.h"

#include <algorithm>

namespace FileManager
{
namespace FileSystem
{

using namespace Common;

static LPCTSTR g_Message_FileWriteError = _T("File write error");

//taken from Panel.h

struct CVolSeqName
{
	CString UnchangedPart;
	CString ChangedPart;
	CVolSeqName(): ChangedPart(_T("000")) {};

	void SetNumDigits(UINT64 numVolumes)
	{
		ChangedPart = _T("000");
		while (numVolumes > 999)
		{
			numVolumes /= 10;
			ChangedPart += _T('0');
		}
	}

	bool ParseName(const CString &name)
	{
		if (name.Right(2) != _T("01"))
		{
			return false;
		}
		int numLetters = 2;
		while (numLetters < name.GetLength())
		{
			if (name[name.GetLength() - numLetters - 1] != _T('0'))
			{
				break;
			}
			numLetters++;
		}
		UnchangedPart = name.Left(name.GetLength() - numLetters);
		ChangedPart = name.Right(numLetters);
		return true;
	}

	CString GetNextName()
	{
		CString newName;
		int i;
		int numLetters = ChangedPart.GetLength();
		for (i = numLetters - 1; i >= 0; i--)
		{
			TCHAR c = ChangedPart[i];
			if (c == _T('9'))
			{
				c = _T('0');
				newName = c + newName;
				if (i == 0)
				{
					newName = CString(_T('1')) + newName;
				}
				continue;
			}
			c++;
			newName = c + newName;
			i--;
			for (; i >= 0; i--)
			{
				newName = ChangedPart[i] + newName;
			}
			break;
		}
		ChangedPart = newName;
		return UnchangedPart + ChangedPart;
	}
};

static const UINT32 kBufSize = (1 << 20);

class CThreadSplit: public CProgressThreadVirt
{
	HRESULT ProcessVirt();
public:
	CString FilePath;
	CString VolBasePath;
	UINT64 NumVolumes;
	std::vector<UINT64> VolumeSizes;
	CThreadSplit(CProgressThreadVirt::ThreadPoolType* pool) :
		CProgressThreadVirt(pool)
	{}
};

HRESULT CThreadSplit::ProcessVirt()
{
	Common::IO::CInFile inFile;
	if (!inFile.Open(FilePath))
	{
		return GetLastError();
	}
	Common::IO::COutFile outFile;
	CMyBuffer bufferObject;
	if (!bufferObject.Allocate(kBufSize))
	{
		return E_OUTOFMEMORY;
	}
	BYTE* buffer = (BYTE*)(void*)bufferObject;
	UINT64 curVolSize = 0;
	CVolSeqName seqName;
	seqName.SetNumDigits(NumVolumes);
	UINT64 length;
	if (!inFile.GetLength(length))
	{
		return GetLastError();
	}

	CProgressSync &sync = ProgressDialog.Sync;
	sync.SetProgress(length, 0);
	UINT64 pos = 0;

	UINT64 numFiles = 0;
	size_t volIndex = 0;

	HRESULT hRes(S_OK);
	for (;;)
	{
		UINT64 volSize;
		if (volIndex < VolumeSizes.size())
		{
			volSize = VolumeSizes[volIndex];
		}
		else
		{
			volSize = VolumeSizes.back();
		}

		UINT32 needSize = (UINT32)(min((UINT64)kBufSize, volSize - curVolSize));
		UINT32 processedSize;
		if (!inFile.Read(buffer, needSize, processedSize))
		{
			return GetLastError();
		}
		if (processedSize == 0)
		{
			break;
		}
		needSize = processedSize;
		if (curVolSize == 0)
		{
			CString name = VolBasePath;
			name += _T('.');
			name += seqName.GetNextName();
			sync.SetCurrentFileName(name);
			sync.SetNumFilesCur(numFiles++);
			if (!outFile.Create(name, false))
			{
				hRes = GetLastError();
				ErrorPath1 = name;
				return hRes;
			}
		}
		if (!outFile.Write(buffer, needSize, processedSize))
		{
			return GetLastError();
		}
		if (needSize != processedSize)
		{
			throw g_Message_FileWriteError;
		}
		curVolSize += processedSize;
		if (curVolSize == volSize)
		{
			outFile.Close();
			if (volIndex < VolumeSizes.size())
			{
				volIndex++;
			}
			curVolSize = 0;
		}
		pos += processedSize;
		hRes = sync.SetPosAndCheckPaused(pos);
		if(S_OK != hRes)
		{
			return hRes;
		}
	}
	sync.SetNumFilesCur(numFiles);
	return S_OK;
}

void CDirectory::SplitFiles(std::vector<UINT32> &indices)
{
	if (!IsFSFolder())
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_OPERATION_IS_NOT_SUPPORTED, 0x03020208);
		return;
	}
	if (indices.empty())
	{
		return;
	}
	if (indices.size() != 1)
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_SELECT_ONE_FILE, 0x03020A02);
		return;
	}
	int index = indices[0];
	if (IsItemFolder(index))
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_SELECT_ONE_FILE, 0x03020A02);
		return;
	}
	const CString itemName = GetItemName(index);

	CString srcPath = GetCurrentDirectory() + GetItemPrefix(index);
	CString path = srcPath;
	CSplitDialog splitDialog;
	splitDialog.FilePath = GetItemRelPath(index);
	splitDialog.Path = path;
	if (splitDialog.DoModal(m_hWnd) == IDCANCEL)
	{
		return;
	}

	Common::FileSystem::File::CFileInfo fileInfo;
	if (!fileInfo.Find(srcPath + itemName))
	{
		CWindows::MessageBoxMyError(m_hWnd, _T("Can not find file"));
		return;
	}
	if (fileInfo.Size <= splitDialog.VolumeSizes.front())
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_SPLIT_VOL_MUST_BE_SMALLER, 0x03020522);
		return;
	}
	const UINT64 numVolumes = Common::FileSystem::GetNumberOfVolumes(fileInfo.Size, splitDialog.VolumeSizes);
	if (numVolumes >= 100)
	{
		TCHAR s[32];
		_ui64tot(numVolumes, s, 10);
		if (::MessageBoxW(m_hWnd, String::PatternFormat(IDS_SPLIT_CONFIRM_MESSAGE, 0x03020521, s),
						  Lang::LangString(IDS_SPLIT_CONFIRM_TITLE, 0x03020520),
						  MB_YESNOCANCEL | MB_ICONQUESTION) != IDYES)
		{
			return;
		}
	}

	path = splitDialog.Path;
	Common::FileSystem::FileName::NormalizeDirPathPrefix(path);
	if (!Common::FileSystem::CreateComplexDirectory(path))
	{
		CWindows::MessageBoxMyError(m_hWnd, String::PatternFormat(IDS_CANNOT_CREATE_FOLDER, 0x02000603, path));
		return;
	}

	{
		CThreadSplit spliter(&m_pool);
		spliter.NumVolumes = numVolumes;

		CProgressDialog2 &progressDialog = spliter.ProgressDialog;

		CString progressWindowTitle = Lang::LangString(IDS_APP_TITLE, 0x03000000);
		CString title = Lang::LangString(IDS_SPLITTING, 0x03020510);

		progressDialog.ShowCompressionInfo = false;

		progressDialog.MainWindow = m_hWnd;
		progressDialog.MainTitle = progressWindowTitle;
		progressDialog.MainAddTitle = title + CString(_T(" "));
		progressDialog.Sync.SetTitleFileName(itemName);


		spliter.FilePath = srcPath + itemName;
		spliter.VolBasePath = path + itemName;
		spliter.VolumeSizes.assign(splitDialog.VolumeSizes.begin(), splitDialog.VolumeSizes.end());

		if (spliter.Create(title, m_hWnd) != 0)
		{
			return;
		}
	}
}


class CThreadCombine: public CProgressThreadVirt
{
	HRESULT ProcessVirt();
public:
	CString InputDirPrefix;
	CStringVector Names;
	CString OutputPath;
	UINT64 TotalSize;
	CThreadCombine(CProgressThreadVirt::ThreadPoolType* pool) :
		CProgressThreadVirt(pool)
	{}
};

HRESULT CThreadCombine::ProcessVirt()
{
	Common::IO::COutFile outFile;
	HRESULT hRes(S_OK);
	if (!outFile.Create(OutputPath, false))
	{
		hRes = GetLastError();
		ErrorPath1 = OutputPath;
		return hRes;
	}

	CProgressSync &sync = ProgressDialog.Sync;
	sync.SetProgress(TotalSize, 0);

	CMyBuffer bufferObject;
	if (!bufferObject.Allocate(kBufSize))
	{
		return E_OUTOFMEMORY;
	}
	BYTE* buffer = (BYTE*)(void*)bufferObject;
	UINT64 pos = 0;
	for (size_t i = 0; i < Names.size(); i++)
	{
		Common::IO::CInFile inFile;
		const CString nextName = InputDirPrefix + Names[i];
		if (!inFile.Open(nextName))
		{
			hRes = GetLastError();
			ErrorPath1 = nextName;
			return hRes;
		}
		sync.SetCurrentFileName(nextName);
		for (;;)
		{
			UINT32 processedSize;
			if (!inFile.Read(buffer, kBufSize, processedSize))
			{
				hRes = GetLastError();
				ErrorPath1 = nextName;
				return hRes;
			}
			if (processedSize == 0)
			{
				break;
			}
			UINT32 needSize = processedSize;
			if (!outFile.Write(buffer, needSize, processedSize))
			{
				hRes = GetLastError();
				ErrorPath1 = OutputPath;
				return hRes;
			}
			if (needSize != processedSize)
			{
				throw g_Message_FileWriteError;
			}
			pos += processedSize;
			hRes = sync.SetPosAndCheckPaused(pos);
			if(S_OK != hRes)
			{
				return hRes;
			}
		}
	}
	return S_OK;
}

extern void AddValuePair2(UINT resourceID, UINT32 langID, UINT64 num, UINT64 size, CString &s);

static void AddInfoFileName(const CString &name, CString &dest)
{
	dest += _T("\n  ");
	dest += name;
}

void CDirectory::CombineFiles(std::vector<UINT32> &indices)
{
	if (!IsFSFolder())
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_OPERATION_IS_NOT_SUPPORTED, 0x03020208);
		return;
	}
	if (indices.empty())
	{
		return;
	}
	int index = indices[0];
	if (indices.size() != 1 || IsItemFolder(index))
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_COMBINE_SELECT_ONE_FILE, 0x03020620);
		return;
	}
	const CString itemName = GetItemName(index);

	CString srcPath = GetCurrentDirectory() + GetItemPrefix(index);
	CString path = srcPath;

	CVolSeqName volSeqName;
	if (!volSeqName.ParseName(itemName))
	{
		CWindows::MessageBoxErrorLang(m_hWnd, IDS_COMBINE_CANT_DETECT_SPLIT_FILE, 0x03020621);
		return;
	}

	{
		CThreadCombine combiner(&m_pool);

		CString nextName = itemName;
		combiner.TotalSize = 0;
		for (;;)
		{
			Common::FileSystem::File::CFileInfo fileInfo;
			if (!fileInfo.Find(srcPath + nextName) || fileInfo.IsDir())
			{
				break;
			}
			combiner.Names.push_back(nextName);
			combiner.TotalSize += fileInfo.Size;
			nextName = volSeqName.GetNextName();
		}
		if (combiner.Names.size() == 1)
		{
			CWindows::MessageBoxErrorLang(m_hWnd, IDS_COMBINE_CANT_FIND_MORE_THAN_ONE_PART, 0x03020622);
			return;
		}

		if (combiner.TotalSize == 0)
		{
			CWindows::MessageBoxMyError(m_hWnd, _T("No data"));
			return;
		}

		CString info;
		AddValuePair2(IDS_FILES_COLON, 0x02000320, combiner.Names.size(), combiner.TotalSize, info);

		info += _T("\n");
		info += srcPath;

		size_t i;
		for (i = 0; i < combiner.Names.size() && i < 2; i++)
		{
			AddInfoFileName(combiner.Names[i], info);
		}
		if (i != combiner.Names.size())
		{
			if (i + 1 != combiner.Names.size())
			{
				AddInfoFileName(_T("..."), info);
			}
			AddInfoFileName(combiner.Names.back(), info);
		}

		{
			CCopyDialog copyDialog;
			copyDialog.Value = path;
			copyDialog.Title = Lang::LangString(IDS_COMBINE, 0x03020600);
			copyDialog.Title += ' ';
			copyDialog.Title += GetItemRelPath(index);
			copyDialog.Static = Lang::LangString(IDS_COMBINE_TO, 0x03020601);
			copyDialog.Info = info;
			if (copyDialog.DoModal() == IDCANCEL)
			{
				return;
			}
			path = copyDialog.Value;
		}

		Common::FileSystem::FileName::NormalizeDirPathPrefix(path);
		if (!Common::FileSystem::CreateComplexDirectory(path))
		{
			CWindows::MessageBoxMyError(m_hWnd, String::PatternFormat(IDS_CANNOT_CREATE_FOLDER, 0x02000603, path));
			return;
		}

		CString outName = volSeqName.UnchangedPart;
		while (!outName.IsEmpty())
		{
			int lastIndex = outName.GetLength() - 1;
			if (outName[lastIndex] != _T('.'))
			{
				break;
			}
			outName.Delete(lastIndex);
		}
		if (outName.IsEmpty())
		{
			outName = _T("file");
		}

		Common::FileSystem::File::CFileInfo fileInfo;
		CString destFilePath = path + outName;
		combiner.OutputPath = destFilePath;
		if (fileInfo.Find(destFilePath))
		{
			CWindows::MessageBoxMyError(m_hWnd, String::PatternFormat(IDS_FILE_EXIST, 0x03020A04, destFilePath));
			return;
		}

		CProgressDialog2 &progressDialog = combiner.ProgressDialog;
		progressDialog.ShowCompressionInfo = false;

		CString progressWindowTitle = Lang::LangString(IDS_APP_TITLE, 0x03000000);
		CString title = Lang::LangString(IDS_COMBINING, 0x03020610);

		progressDialog.MainWindow = m_hWnd;
		progressDialog.MainTitle = progressWindowTitle;
		progressDialog.MainAddTitle = title + CString(_T(" "));

		combiner.InputDirPrefix = srcPath;

		if (combiner.Create(title, m_hWnd) != 0)
		{
			return;
		}
	}
	
}


} //namespace FileSystem
} //namespace FileManager